#ifndef PUDGE_GRFX_RENDER_HPP
#define PUDGE_GRFX_RENDER_HPP

#include <windowsx.h>
#include <windows.h>
#include <GL/glew.h>

#include <pudge/grfx/shader.hpp>
#include <pudge/grfx/texture.hpp>
#include <pudge/grfx/mesh.hpp>
#include <pudge/math/mat.hpp>
#include <string>

namespace pudge { namespace grfx {

	struct grfx_context {} ;

	template<class T> struct ogl_primitive{};

	template<> struct ogl_primitive<char> 
	{ 	static const GLenum glenum = GL_BYTE; };

	template<> struct ogl_primitive<short> 
	{ 	static const GLenum glenum = GL_SHORT; };

	template<> struct ogl_primitive<long> 
	{	static const GLenum glenum = GL_INT; };

	template<> struct ogl_primitive<unsigned char> 
	{	static const GLenum glenum = GL_UNSIGNED_BYTE; };

	template<> struct ogl_primitive<unsigned short> 
	{	static const GLenum glenum = GL_UNSIGNED_SHORT; };

	template<> struct ogl_primitive<unsigned long> 
	{	static const GLenum glenum = GL_UNSIGNED_INT; };

	template<> struct ogl_primitive<float> 
	{	static const GLenum glenum = GL_FLOAT; };

	template<> struct ogl_primitive<double> 
	{	static const GLenum glenum = GL_DOUBLE; };

	template<int I> struct ogl_multi_texture {};

	template<> struct ogl_multi_texture<0>
	{	static const GLenum type = GL_TEXTURE0; };

	template<> struct ogl_multi_texture<1>
	{	static const GLenum type = GL_TEXTURE1; };

	template<> struct ogl_multi_texture<2>
	{	static const GLenum type = GL_TEXTURE2; };

	template<> struct ogl_multi_texture<3>
	{	static const GLenum type = GL_TEXTURE3; };

	template<> struct ogl_multi_texture<4>
	{	static const GLenum type = GL_TEXTURE4; };

	struct render_attributes_
	{
		typedef unsigned int uint;

		/// define our binder template

		template<class T> struct bind_ {};

		/// template special binds texture2d objects

		template<class Traits> struct bind_<texture2d<Traits>>
		{
			template<class Tex> void operator()(Tex & t, uint & i, uint & m) const
			{
				switch(m++)
				{
				case 0:
					glActiveTexture(GL_TEXTURE0);
					glClientActiveTexture(GL_TEXTURE0);
					break;
				case 1:
					glActiveTexture(GL_TEXTURE1);
					glClientActiveTexture(GL_TEXTURE1);
					break;
				case 2:
					glActiveTexture(GL_TEXTURE2);
					glClientActiveTexture(GL_TEXTURE2);
					break;
				case 3:
					glActiveTexture(GL_TEXTURE3);
					glClientActiveTexture(GL_TEXTURE3);
					break;
				};

				glEnable(GL_TEXTURE_2D);
				glBindTexture(GL_TEXTURE_2D, t.id());
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
			}
		};

		/// template special binds a vertex_attribute

		template<class Traits> struct bind_<vertex_attribute<Traits>>
		{
			template<class A> 
			void operator()(A & a, uint & i, uint & m ) const
			{
				typedef attribute_meta<A::value_type> Meta;
				typedef ogl_primitive<Meta::value_type> Primitive;

				glBindBuffer(GL_ARRAY_BUFFER, a.id());

				glVertexAttribPointer
					(	i
					,	Meta::size
					,	Primitive::glenum
					,	false
					,	0
					,	0 );

				glEnableVertexAttribArray(i++);

				glBindBuffer(GL_ARRAY_BUFFER, 0);

				test_gl_error("bind attribute");
			}
		};

		template<class T> struct disable_ {};

		template<class Traits> struct disable_<vertex_attribute<Traits>>
		{
			void operator() (uint & i, uint & m) const
			{
				glDisableVertexAttribArray(i++);
			}
		};
		template<class Traits> struct disable_<texture2d<Traits>>
		{
			void operator() (uint & i, uint & m) const
			{
				switch(m++)
				{
				case 0:
					glActiveTexture(GL_TEXTURE0);
					glClientActiveTexture(GL_TEXTURE0);
					break;
				case 1:
					glActiveTexture(GL_TEXTURE1);
					glClientActiveTexture(GL_TEXTURE1);
					break;
				case 2:
					glActiveTexture(GL_TEXTURE2);
					glClientActiveTexture(GL_TEXTURE2);
					break;
				case 3:
					glActiveTexture(GL_TEXTURE3);
					glClientActiveTexture(GL_TEXTURE3);
					break;
				};

				glDisable(GL_TEXTURE_2D);
			}
		};

		template<class Traits> 
		void operator() (const uint cnt, index_attribute<Traits> & a ) const
		{
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, a.id());
			
			const GLenum Index = ogl_primitive<index_attribute<Traits>::value_type>::glenum;

			if(		Index != GL_UNSIGNED_INT
				&&	Index != GL_UNSIGNED_SHORT
				&&	Index != GL_UNSIGNED_BYTE )
				throw -1;

			glDrawElements
				(	index_attribute<Traits>::poly_type::glenum
				,	cnt
				,	Index
				,	0 );

			test_gl_error("render_mesh_");
		}
		template<class A0, class A1> 
		void operator()(const uint cnt, A0 & a0, A1 & a1 ) const
		{
			uint i = 0;
			uint m = 0;
			bind_<A1> () (a1,i,m);
			(*this)(cnt,a0);

			i = 0;
			m = 0;
			disable_<A1> () (i,m);
		}
		template<class A0, class A1, class A2> 
		void operator()(const uint cnt, A0 & a0, A1 & a1, A2 & a2) const
		{
			uint i = 0;
			uint m = 0;
			bind_<A1> () (a1,i,m);
			bind_<A2> () (a2,i,m);
			(*this)(cnt,a0);

			i = 0;
			m = 0;
			disable_<A1> () (i,m);
			disable_<A2> () (i,m);
		}
		template<class A0, class A1, class A2, class A3> 
		void operator()(const uint cnt, A0 & a0, A1 & a1, A2 & a2, A3 & a3) const
		{
			uint i = 0;
			uint m = 0;
			bind_<A1> () (a1,i,m);
			bind_<A2> () (a2,i,m);
			bind_<A3> () (a3,i,m);
			(*this)(cnt,a0);

			i = 0;
			m = 0;
			disable_<A1> () (i,m);
			disable_<A2> () (i,m);
			disable_<A3> () (i,m);
		}
		template<class A0, class A1, class A2, class A3, class A4> 
		void operator()(const uint cnt, A0 & a0, A1 & a1, A2 & a2, A3 & a3, A4 & a4) const
		{
			uint i = 0;
			uint m = 0;
			bind_<A1> () (a1,i,m);
			bind_<A2> () (a2,i,m);
			bind_<A3> () (a3,i,m);
			bind_<A4> () (a4,i,m);
			(*this)(cnt,a0);

			i = 0;
			m = 0;
			disable_<A1> () (i,m);
			disable_<A2> () (i,m);
			disable_<A3> () (i,m);
			disable_<A4> () (i,m);
		}
	} const render_attributes;
}}
#endif